// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/media/audio_repetition_detector.h"

#include <stddef.h>

#include <map>
#include <memory>

#include "base/bind.h"
#include "base/macros.h"
#include "base/rand_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

namespace {
    const int kDefaultMinLengthMs = 1;
    const size_t kDefaultMaxFrames = 480; // 10 ms * 48 kHz

    // Sample rate used in many tests. We choose a special sample rate in order to
    // make the test signal obvious.
    const int kSampleRateHz = 1000;

}

class AudioRepetitionDetectorForTest : public AudioRepetitionDetector {
public:
    AudioRepetitionDetectorForTest(int min_length_ms, size_t max_frames,
        const int* look_back_times,
        size_t num_look_back)
        : AudioRepetitionDetector(
            min_length_ms, max_frames,
            std::vector<int>(look_back_times, look_back_times + num_look_back),
            base::Bind(&AudioRepetitionDetectorForTest::OnRepetitionDetected,
                base::Unretained(this)))
    {
    }

    int GetCount(int look_back_ms) const
    {
        auto it = counters_.find(look_back_ms);
        return it == counters_.end() ? 0 : it->second;
    }

    void ResetCounters()
    {
        counters_.clear();
    }

private:
    void OnRepetitionDetected(int look_back_ms)
    {
        auto it = counters_.find(look_back_ms);
        if (it == counters_.end()) {
            counters_.insert(std::pair<int, size_t>(look_back_ms, 1));
            return;
        }
        it->second++;
    }

    std::map<int, size_t> counters_;
};

class AudioRepetitionDetectorTest : public ::testing::Test {
public:
    AudioRepetitionDetectorTest()
        : detector_(nullptr)
    {
    }

protected:
    struct ExpectedCount {
        int look_back_ms;
        int count;
    };

    // Verify if the counts on the repetition patterns match expectation after
    // injecting a signal. No reset on the counters
    void Verify(const ExpectedCount* expected_counts, size_t num_patterns,
        const float* tester, size_t num_frames,
        int sample_rate_hz, size_t channels = 1)
    {
        detector_->Detect(tester, num_frames, channels, sample_rate_hz);
        for (size_t idx = 0; idx < num_patterns; ++idx) {
            const int look_back_ms = expected_counts[idx].look_back_ms;
            EXPECT_EQ(expected_counts[idx].count, detector_->GetCount(look_back_ms))
                << "Repetition with look back "
                << look_back_ms
                << " ms counted wrong.";
        }
    }

    void VerifyStereo(const ExpectedCount* expected_counts, size_t num_patterns,
        const float* tester, size_t num_frames,
        int sample_rate_hz)
    {
        static const size_t kNumChannels = 2;

        // Get memory to store interleaved stereo.
        std::unique_ptr<float[]> tester_stereo(
            new float[num_frames * kNumChannels]);

        for (size_t idx = 0; idx < num_frames; ++idx, ++tester) {
            for (size_t channel = 0; channel < kNumChannels; ++channel)
                tester_stereo[idx * kNumChannels + channel] = *tester;
        }

        Verify(expected_counts, num_patterns, tester_stereo.get(),
            num_frames, sample_rate_hz, kNumChannels);
    }

    void SetDetector(int min_length_ms, size_t max_frames,
        const int* look_back_times, size_t num_look_back)
    {
        detector_.reset(new AudioRepetitionDetectorForTest(min_length_ms,
            max_frames,
            look_back_times,
            num_look_back));
    }

    void ResetCounters()
    {
        detector_->ResetCounters();
    }

private:
    std::unique_ptr<AudioRepetitionDetectorForTest> detector_;
};

TEST_F(AudioRepetitionDetectorTest, Basic)
{
    // Check that one look back time will registered only once.
    const int kLookbackTimes[] = { 3, 3, 3, 3 };

    const float kTestSignal[] = { 1, 2, 3, 1, 2, 3 };
    const ExpectedCount kExpectedCounts_1[] = {
        { 3, 1 }
    };
    const ExpectedCount kExpectedCounts_2[] = {
        { 3, 1 }
    };

    SetDetector(kDefaultMinLengthMs, kDefaultMaxFrames, kLookbackTimes,
        arraysize(kLookbackTimes));
    Verify(kExpectedCounts_1, arraysize(kExpectedCounts_1), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
    Verify(kExpectedCounts_2, arraysize(kExpectedCounts_2), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
    ResetCounters();

    VerifyStereo(kExpectedCounts_1, arraysize(kExpectedCounts_1), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
    VerifyStereo(kExpectedCounts_2, arraysize(kExpectedCounts_2), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
}

TEST_F(AudioRepetitionDetectorTest, StereoOutOfSync)
{
    const int kLookbackTimes[] = { 3 };
    const float kTestSignal[] = {
        1, 1,
        2, 2,
        3, 3,
        1, 1,
        2, 2,
        3, 1
    };
    const ExpectedCount kExpectedCounts[] = {
        { 3, 0 }
    };

    // By default, any repetition longer than 1 ms (1 sample at 1000 Hz) will be
    // counted as repetition. This test needs to make it longer.
    SetDetector(3, kDefaultMaxFrames, kLookbackTimes, arraysize(kLookbackTimes));
    Verify(kExpectedCounts, arraysize(kExpectedCounts), kTestSignal,
        arraysize(kTestSignal) / 2, kSampleRateHz, 2);
}

TEST_F(AudioRepetitionDetectorTest, IncompletePattern)
{
    const int kLookbackTimes[] = { 3 };
    const float kTestSignal[] = { 1, 2, 1, 2, 3, 1, 2, 3 };
    const ExpectedCount kExpectedCounts[] = {
        { 3, 1 },
    };

    SetDetector(kDefaultMinLengthMs, kDefaultMaxFrames, kLookbackTimes,
        arraysize(kLookbackTimes));
    Verify(kExpectedCounts, arraysize(kExpectedCounts), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
    ResetCounters();
    VerifyStereo(kExpectedCounts, arraysize(kExpectedCounts), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
}

TEST_F(AudioRepetitionDetectorTest, PatternLongerThanFrame)
{
    // To make the test signal most obvious, we choose a special sample rate.
    const int kSampleRateHz = 1000;

    const int kLookbackTimes[] = { 6 };
    const float kTestSignal_1[] = { 1, 2, 3, 4, 5 };
    const float kTestSignal_2[] = { 6, 1, 2, 3, 4, 5, 6 };
    const ExpectedCount kExpectedCounts_1[] = {
        { 6, 0 },
    };
    const ExpectedCount kExpectedCounts_2[] = {
        { 6, 1 },
    };

    SetDetector(kDefaultMinLengthMs, kDefaultMaxFrames, kLookbackTimes,
        arraysize(kLookbackTimes));
    Verify(kExpectedCounts_1, arraysize(kExpectedCounts_1), kTestSignal_1,
        arraysize(kTestSignal_1), kSampleRateHz);
    Verify(kExpectedCounts_2, arraysize(kExpectedCounts_2), kTestSignal_2,
        arraysize(kTestSignal_2), kSampleRateHz);
    ResetCounters();
    VerifyStereo(kExpectedCounts_1, arraysize(kExpectedCounts_1), kTestSignal_1,
        arraysize(kTestSignal_1), kSampleRateHz);
    VerifyStereo(kExpectedCounts_2, arraysize(kExpectedCounts_2), kTestSignal_2,
        arraysize(kTestSignal_2), kSampleRateHz);
}

TEST_F(AudioRepetitionDetectorTest, TwoPatterns)
{
    const int kLookbackTimes[] = { 3, 4 };
    const float kTestSignal[] = { 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4 };
    const ExpectedCount kExpectedCounts[] = {
        // 1,2,3 belongs to both patterns.
        { 3, 1 },
        { 4, 1 }
    };

    SetDetector(kDefaultMinLengthMs, kDefaultMaxFrames, kLookbackTimes,
        arraysize(kLookbackTimes));
    Verify(kExpectedCounts, arraysize(kExpectedCounts), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
    ResetCounters();
    VerifyStereo(kExpectedCounts, arraysize(kExpectedCounts), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
}

TEST_F(AudioRepetitionDetectorTest, MaxFramesShorterThanInput)
{
    // To make the test signal most obvious, we choose a special sample rate.
    const int kSampleRateHz = 1000;

    const int kLookbackTimes[] = { 3, 4 };
    const float kTestSignal[] = { 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4 };
    const ExpectedCount kExpectedCounts[] = {
        // 1,2,3 belongs to both patterns.
        { 3, 1 },
        { 4, 1 }
    };

    // length of kTestSignal is 11 but I set maximum frames to be 2. The detection
    // should still work.
    SetDetector(kDefaultMinLengthMs, 2, kLookbackTimes, arraysize(kLookbackTimes));
    Verify(kExpectedCounts, arraysize(kExpectedCounts), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
    ResetCounters();
    VerifyStereo(kExpectedCounts, arraysize(kExpectedCounts), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
}

TEST_F(AudioRepetitionDetectorTest, NestedPatterns)
{
    const int kLookbackTimes[] = { 6, 3 };
    const float kTestSignal[] = { 1, 2, 3, 1, 2, 3 };
    const ExpectedCount kExpectedCounts_1[] = {
        { 3, 1 },
        { 6, 0 }
    };
    const ExpectedCount kExpectedCounts_2[] = {
        { 3, 1 },
        { 6, 1 }
    };

    SetDetector(kDefaultMinLengthMs, kDefaultMaxFrames, kLookbackTimes,
        arraysize(kLookbackTimes));
    Verify(kExpectedCounts_1, arraysize(kExpectedCounts_1), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
    Verify(kExpectedCounts_2, arraysize(kExpectedCounts_2), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
    ResetCounters();
    VerifyStereo(kExpectedCounts_1, arraysize(kExpectedCounts_1), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
    VerifyStereo(kExpectedCounts_2, arraysize(kExpectedCounts_2), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
}

TEST_F(AudioRepetitionDetectorTest, NotFullLengthPattern)
{
    const int kLookbackTimes[] = { 4 };
    const float kTestSignal[] = { 1, 2, 3, -1, 1, 2, 3, -2 };
    const ExpectedCount kExpectedCounts[] = {
        { 4, 1 },
    };

    SetDetector(kDefaultMinLengthMs, kDefaultMaxFrames, kLookbackTimes,
        arraysize(kLookbackTimes));
    Verify(kExpectedCounts, arraysize(kExpectedCounts), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
    ResetCounters();
    VerifyStereo(kExpectedCounts, arraysize(kExpectedCounts), kTestSignal,
        arraysize(kTestSignal), kSampleRateHz);
}

TEST_F(AudioRepetitionDetectorTest, DcCountOrNot)
{
    const int kLookbackTimes[] = { 3 };
    const float kDc = 1.2345f;
    const float kTestSignal_1[] = { kDc, kDc, kDc, kDc, kDc, kDc };
    const float kTestSignal_2[] = { kDc, 1, 2, kDc, 1, 2 };
    const ExpectedCount kExpectedCounts_1[] = {
        // Full zeros won't count.
        { 3, 0 },
    };
    const ExpectedCount kExpectedCounts_2[] = {
        // Partial zero will count.
        { 3, 1 },
    };

    SetDetector(kDefaultMinLengthMs, kDefaultMaxFrames, kLookbackTimes,
        arraysize(kLookbackTimes));
    Verify(kExpectedCounts_1, arraysize(kExpectedCounts_1), kTestSignal_1,
        arraysize(kTestSignal_1), kSampleRateHz);
    Verify(kExpectedCounts_2, arraysize(kExpectedCounts_2), kTestSignal_2,
        arraysize(kTestSignal_2), kSampleRateHz);
    ResetCounters();
    VerifyStereo(kExpectedCounts_1, arraysize(kExpectedCounts_1), kTestSignal_1,
        arraysize(kTestSignal_1), kSampleRateHz);
    VerifyStereo(kExpectedCounts_2, arraysize(kExpectedCounts_2), kTestSignal_2,
        arraysize(kTestSignal_2), kSampleRateHz);
}

// Previous tests use short signal to test the detection algorithm, this one
// tests a normal frame size
TEST_F(AudioRepetitionDetectorTest, NormalSignal)
{
    const int kNormalSampleRateHz = 44100;
    // Let the signal be "*(4ms)-A(13ms)-*(100ms)-A", where * denotes random
    // samples.
    const size_t kPreSamples = kNormalSampleRateHz * 4 / 1000;
    const size_t kRepSamples = kNormalSampleRateHz * 13 / 1000;
    const size_t kSkipSamples = kNormalSampleRateHz * 100 / 1000;
    const size_t kSamples = kPreSamples + kRepSamples * 2 + kSkipSamples;

    const int kLookbackTimes[] = { 80, 90, 100, 110, 120 };

    float test_signal[kSamples];
    size_t idx = 0;
    for (; idx < kPreSamples + kRepSamples + kSkipSamples; ++idx)
        test_signal[idx] = static_cast<float>(base::RandDouble());

    for (; idx < kSamples; ++idx)
        test_signal[idx] = test_signal[idx - kSkipSamples];

    ExpectedCount expect_counts[arraysize(kLookbackTimes)];
    for (size_t i = 0; i < arraysize(kLookbackTimes); ++i) {
        expect_counts[i].look_back_ms = kLookbackTimes[i];
        expect_counts[i].count = 0;
    }

    // We only expect a repetition with 100 ms look back time.
    expect_counts[2].count = 1;

    SetDetector(kDefaultMinLengthMs, kDefaultMaxFrames, kLookbackTimes,
        arraysize(kLookbackTimes));
    Verify(expect_counts, arraysize(expect_counts), test_signal, kSamples,
        kNormalSampleRateHz);
}

} // namespace content
